﻿using System;
using System.Collections.Generic;

namespace JH
{
    /// <summary>
    /// 固定长度的哈希集合
    /// </summary>
    public class FixUniqueArray<T> : IFixedContainer<T>
    {
        /// <summary>
        ///  溢出模式
        /// </summary>
        public OverflowMode Mode { get; }

        /// <summary>
        /// 哈希集合的比较器
        /// </summary>
        private readonly IEqualityComparer<T> _equalityComparer;

        /// <summary>
        /// 底层固定数组，用于存储数据
        /// </summary>
        private readonly FixedArray<T> _fixedArray;

        /// <summary>
        /// 哈希集合，用于去重元素
        /// </summary>
        private readonly HashSet<T> _hashSet;

        /// <summary>
        ///  当元素因为存储容量满了，添加新元素而被丢弃时触发
        /// </summary>
        public event Action<T> OnItemDiscard;

        /// <summary>
        /// 创建一个新的固定长度哈希集合
        /// </summary>
        public FixUniqueArray(int maxSize, IEqualityComparer<T> equalityComparer = null,
            OverflowMode mode = OverflowMode.DiscardOldestAdd)
        {
            // 设置溢出模式
            Mode = mode;

            // 初始化比较器、固定数组和哈希集合
            if (maxSize <= 0) throw new ArgumentOutOfRangeException(nameof(maxSize), "最大容量必须大于0。");

            _equalityComparer = equalityComparer ?? EqualityComparer<T>.Default;
            _fixedArray = new FixedArray<T>(maxSize, mode);
            _hashSet = new HashSet<T>(maxSize, equalityComparer);
        }


        /// <summary>
        /// 添加元素到集合中，如果元素已经存在，则不添加
        /// </summary>
        public void Add(T item)
        {
            // 如果哈希集合中已经有这个元素，则直接返回
            if (_hashSet.Contains(item)) return;

            // 处理容量溢出
            this.HandleOverflow();

            // 添加到哈希集合中
            _hashSet.Add(item);

            // 添加元素到固定数组
            _fixedArray.Add(item);
        }

        /// <summary>
        ///  移除集合中的指定元素
        /// </summary>
        public bool Remove(T item)
        {
            //如果哈希集合中没有这个元素，则返回false       
            if (!_hashSet.Remove(item)) return false;

            // 从固定数组中移除元素
            int index = _fixedArray.FindIndex(item, _equalityComparer);
            if (index >= 0) _fixedArray.RemoveAt(index);
            return true;
        }

        /// <summary>
        ///  查找元素在集合中的索引。
        /// </summary>
        public int IndexOf(T item)
        {
            // 查找元素在集合中的索引
            return _fixedArray.FindIndex(item, _equalityComparer);
        }

        /// <summary>
        /// 插入元素到指定位置。
        /// </summary>
        public void Insert(int index, T item)
        {
            //检测索引合法性
            if (index < 0 || index > _fixedArray.Count)
                throw new ArgumentOutOfRangeException(nameof(index), "索引超出范围！");

            // 如果元素已经存在，则不插入
            if (_hashSet.Contains(item))
            {
                //如果集合中已经存在该元素，并且索引和插入索引不一致，则不允许重复插入
                var findIndex = _fixedArray.FindIndex(item, _equalityComparer);
                if (findIndex != index)
                    throw new InvalidOperationException($"已存在元素{item}，无法再次插入。请使用 TryInsert 方法以获取插入结果。");

                // 如果要插入元素的索引和元素都和现有元素相同，则不需要插入
                return;
            }

            // 处理容量溢出
            this.HandleOverflow();

            // 插入元素到固定数组
            _fixedArray.Insert(index, item);

            // 添加到哈希集合中
            _hashSet.Add(item);
        }

        /// <summary>
        /// 移除指定索引处的元素，并从哈希集合中删除该元素。
        /// </summary>
        public void RemoveAt(int index)
        {
            // 检查索引是否有效
            if (index < 0 || index >= _fixedArray.Count)
                throw new ArgumentOutOfRangeException(nameof(index), "索引超出范围！");

            // 移除元素
            Remove(_fixedArray[index]);
        }

        /// <summary>
        ///  获取或设置指定索引处的元素。
        /// </summary>
        public T this[int index]
        {
            get => _fixedArray[index];
            set => Insert(index, value);
        }

        /// <summary>
        /// 检查集合中是否包含某个元素。
        /// </summary>
        public bool Contains(T item)
        {
            return _hashSet.Contains(item);
        }

        /// <summary>
        ///  将集合中的元素复制到指定的数组中，从指定的索引开始。
        /// </summary>
        public void CopyTo(T[] array, int arrayIndex)
        {
            // 将固定数组中的元素复制到目标数组中
            _fixedArray.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// 清空集合，重置哈希集合。
        /// </summary>
        public void Clear()
        {
            _fixedArray.Clear();
            _hashSet.Clear();
        }

        /// <summary>
        /// 获取集合中的元素数量。
        /// </summary>
        public int Count => _fixedArray.Count;

        /// <summary>
        /// 获取集合的容量限制。
        /// </summary>
        public int MaxSize => _fixedArray.MaxSize;

        /// <summary>
        /// 获取集合是否只读。
        /// </summary>
        public bool IsReadOnly => false;

        /// <summary>
        /// 枚举器，返回集合中的元素。
        /// </summary>
        public IEnumerator<T> GetEnumerator()
        {
            return _fixedArray.GetEnumerator();
        }

        /// <summary>
        ///  实现 IEnumerable 接口，返回集合的枚举器。
        /// </summary>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        ///  根据溢出行为枚举，获取需要丢弃的元素
        /// </summary>
        T IFixedContainer<T>.GetDiscardItem()
        {
            //对于当前集合，丢弃首个元素，和丢弃最早添加的元素是等价的
            //丢弃末尾元素，和丢弃最新添加的元素是等价的
            // 是否是丢弃首个元素
            bool isFirstDiscard = Mode is OverflowMode.DiscardFirst or OverflowMode.DiscardOldestAdd;

            // 根据溢出模式，决定丢弃哪个元素
            return isFirstDiscard ? this[0] : this[^1];
        }

        /// <summary>
        ///  引发丢弃事件
        /// </summary>
        void IFixedContainer<T>.InvokeEvent(T item)
        {
            OnItemDiscard?.Invoke(item);
        }
    }
}